package com.hae.shop.micoshopmemberservice.controller;

import com.hae.shop.common.base.Response;
import com.hae.shop.common.constant.Constants;
import com.hae.shop.common.util.BeanConvertUtil;
import com.hae.shop.common.util.MD5Util;
import com.hae.shop.common.util.transaction.RedisDataSoureceTransaction;
import com.hae.shop.member.api.MemberService;
import com.hae.shop.member.model.reqDto.*;
import com.hae.shop.member.model.respDto.UserLoginRespDto;
import com.hae.shop.micoshopmemberservice.feign.WechatFeign;
import com.hae.shop.micoshopmemberservice.mapper.UserMapper;
import com.hae.shop.micoshopmemberservice.mapper.UserTokenMapper;
import com.hae.shop.micoshopmemberservice.mapper.model.UserDo;
import com.hae.shop.micoshopmemberservice.mapper.model.UserTokenDo;
import com.hae.shop.micoshopmemberservice.util.TokenUtil;
import com.hae.shop.wechat.model.respDto.AppInfoRespDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@Validated
public class MemberController implements MemberService {

    @Autowired
    private WechatFeign wechatFeign;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserTokenMapper userTokenMapper;

    @Autowired
    private TokenUtil tokenUtil;

    @Autowired
    private RedisDataSoureceTransaction manualTransaction;

    @GetMapping("/getWechatInfoByMember")
    public String getWechatInfoByMember() {
        StringBuilder result = new StringBuilder();
        Response<AppInfoRespDto> appInfoDtoResponse = wechatFeign.getApp();
        if (appInfoDtoResponse.isSuccess())
            result.append(appInfoDtoResponse.getResult().getAppId() + ":" + appInfoDtoResponse.getResult().getAppName());
        else {
            result.append(appInfoDtoResponse.getErrorMsg());
        }
        return result.toString();
    }

    /**
     * 会员注册接口实现
     * @param userRegistryReqDto 会员信息 json类参数必须使用@RequestBody，否则无法传入参数
     * @param registCode 微信注册码
     * @return
     */
    @Override
    @PostMapping("/register")
    public Response<Boolean> register(@RequestBody UserRegistryReqDto userRegistryReqDto,
                                      @RequestParam("registCode") String registCode) {
        Response result = new Response(Boolean.FALSE);
        // 将密码采用MD5加密
        userRegistryReqDto.setPassword(MD5Util.MD5(userRegistryReqDto.getPassword()));
        // 调用微信接口,验证注册码是否正确
        Response<Boolean> resultVerificaWeixinCode = wechatFeign.verifyCode(userRegistryReqDto.getMobile(),
                registCode);
        if (!resultVerificaWeixinCode.getResult()) { //isSuccess无法传递原因
            result.setErrorMsg(resultVerificaWeixinCode.getErrorMsg());
            return result;
        }
        //调用mybaties服务
        int registerResult = userMapper.register(BeanConvertUtil.EToT(userRegistryReqDto, UserDo.class));
        if (registerResult < 1) {
            result.setErrorMsg("注册失败");
            return result;
        }
        result.setResult(Boolean.TRUE);
        return result;

    }

    @Override
    @PostMapping("/existMobile")
    public Response<Boolean> existMobile(@RequestParam("mobile") String mobile) {
        Response result = new Response(Boolean.FALSE);
        //调用mybaties服务
        UserRegistryReqDto userRegistryReqDto = userMapper.existMobile(mobile);
        if (userRegistryReqDto == null) {
            result.setErrorMsg("用户不存在!");
            return result;
        }
        // 注意需要将敏感数据（密码、邮箱）进行脱敏
        result.setResult(Boolean.TRUE);
        return result;
    }

    @Override
    //@Transactional
    @PostMapping("/login")
    public Response<UserLoginRespDto> login(@RequestBody UserLoginReqDto userLoginReqDto) throws Exception {
        Response result = new Response();
        //1.使用手机号和加密后的密码查询数据库
        Long memberId = userMapper.login(userLoginReqDto.getMobile(), MD5Util.MD5(userLoginReqDto.getPassword()));
        if (StringUtils.isEmpty(memberId)) {
            result.setErrorMsg("手机号或密码错误");
        } else {
            UserLoginRespDto loginRespDto = loginCore(memberId, userLoginReqDto.getLoginType(), userLoginReqDto.getDeviceInfor());
            if (null != loginRespDto) {
                result.setResult(loginRespDto);
            }
        }
        return result;
    }

    @Override
    @PostMapping("/bindloginByQQ")
    public Response<UserLoginRespDto> bindloginByQQ(@RequestBody QQBindLoginReqDto userLoginReqDto) throws Exception {
        Response result = new Response();
        //1.开启事务
        TransactionStatus transactionStatus = manualTransaction.begin();
        try {
            // 2.根据联合登录类型设置关联id，并在数据库中增加一条用户记录
            userLoginReqDto.setPassword(MD5Util.MD5(userLoginReqDto.getPassword()));
            UserDo userDo = BeanConvertUtil.EToT(userLoginReqDto, UserDo.class);
            if (Constants.UNION_LOGIN_TYPE_QQ.equals(userLoginReqDto.getType())) {
                userDo.setQqOpenId(userLoginReqDto.getOpenId());
            } else {
                userDo.setWxOpenId(userLoginReqDto.getOpenId());
            }
            //调用mybaties服务
            int registerResult = userMapper.register(BeanConvertUtil.EToT(userLoginReqDto, UserDo.class));
            if (registerResult < 1) {
                throw new RuntimeException("数据库插入异常");
            }

            // 3.查询新增记录的member_Id
            Long memberId = userMapper.login(userLoginReqDto.getMobile(), MD5Util.MD5(userLoginReqDto.getPassword()));

            // 4.生成token并token和设备信息存入在数据库中
            String token = tokenUtil.createToken(Constants.MEMBER_TOKEN_KEYPREFIX, memberId + "",
                    Constants.MEMBRE_LOGIN_TOKEN_TIME);
            UserTokenDo userTokenDo = new UserTokenDo();
            userTokenDo.setToken(token);
            userTokenDo.setDeviceInfor(userLoginReqDto.getDeviceInfor());
            // redis 使用事务时暂时不会提交新生成的token，但会返回值，也需要根据数据库插入是否成功决定是否提交
            int insertResult = userTokenMapper.insertUserToken(userTokenDo);
            if (insertResult < 1) {
                // 数据库没有提交事务前虽然数据还没有被删除，但返回的依然是正确值。若返回不正确，则抛出异常进行回滚。
                throw new RuntimeException("数据库插入异常");
            }

            // 5.返回
            UserLoginRespDto userLoginRespDto = new UserLoginRespDto();
            userLoginRespDto.setUserid(memberId);
            userLoginRespDto.setToken(token);
            result.setResult(userLoginRespDto);

            // 6.提交事务
            manualTransaction.commit(transactionStatus);
        } catch (Exception e) {
            // 6.回滚事务并抛出异常
            manualTransaction.rollback(transactionStatus);
            throw e;
        }
        return result;
    }

    @Override
    public Response<UserLoginRespDto> autoLoginByQQ(@Valid QQAutoLoginReqDto qqAutoLoginReqDto) throws Exception {
        // 默认返回new loginRespDto
        UserLoginRespDto loginRespDto;
        Response result = new Response();
        // 使用qq_openid查询是否有绑定用户
        Long memberId = userMapper.selectUserByQQId(qqAutoLoginReqDto.getQqOpenId());
        if (!StringUtils.isEmpty(memberId)) {
            // 有绑定用户时，构造loginCoreReqDto进行登录
            loginRespDto = loginCore(memberId, qqAutoLoginReqDto.getLoginType(), qqAutoLoginReqDto.getDeviceInfor());
        } else {
            loginRespDto = new UserLoginRespDto();
        }
        result.setResult(loginRespDto);
        return result;
    }

    @Override
    @PostMapping("/loginBytoken")
    public Response<UserLoginRespDto> loginBytoken(@RequestParam("token") String token) {
        Response<UserLoginRespDto> response = new Response<>();
        String memberId = tokenUtil.getToken(token);
        if (StringUtils.isEmpty(memberId)) {
            response.setErrorMsg("token不存在或已失效，请重新登录");
            return response;
        }
        UserLoginRespDto userLoginRespDto = BeanConvertUtil.EToT(userMapper.findByUserId(Long.parseLong(memberId)), UserLoginRespDto.class);
        if (null == userLoginRespDto) {
            response.setErrorMsg("无该token对应的用户信息");
            return response;
        }
        userLoginRespDto.setToken(token); //token不存在于userLoginRespDto中
        response.setResult(userLoginRespDto);
        return response;
    }

    @Override
    @PostMapping("/loginout")
    public Response<Boolean> loginout(@RequestParam("token") String token) throws Exception {
        Response<Boolean> response = new Response<>(Boolean.FALSE);
        // 1. 查询token是否有效
        String memberId = tokenUtil.getToken(token);
        if (StringUtils.isEmpty(memberId)) {
            response.setErrorMsg("token不存在或已失效，请重新登录");
            return response;
        }
        // 2. redis删除该token且将数据库该token置为无效
        TransactionStatus transactionStatus = manualTransaction.begin();
        try {
            tokenUtil.removeToken(token);
            int updateResult = userTokenMapper.updateTokenAvailabilityByToken(token);
            if (!(updateResult > 0)) {
                throw new RuntimeException("数据库更新异常");

            }
            // 3.提交事务
            manualTransaction.commit(transactionStatus);
            response.setResult(Boolean.TRUE);
        } catch (Exception e) {
            // 4.回滚事务并抛出异常
            manualTransaction.rollback(transactionStatus);
            throw e;
        }
        return response;
    }

    @Override
    @PostMapping("/loginBySSO")
    public Response<UserLoginRespDto> loginBySSO(@RequestBody UserSSOLoginReqDto userSSOLoginReqDto) {
        Response<UserLoginRespDto> result = new Response<>(new UserLoginRespDto());
        Long memberId = userMapper.login(userSSOLoginReqDto.getMobile(), MD5Util.MD5(userSSOLoginReqDto.getPassword()));
        if (StringUtils.isEmpty(memberId)) {
            result.setErrorMsg("手机号或密码错误");
        } else {

            result.getResult().setUserid(memberId);
        }
        return result;
    }

    /**
     * 生成登录信息，要么成功登录进行返回，要么抛出异常
     * @param memberId
     * @param loginType
     * @param deviceInfor
     * @return
     * @throws Exception
     */
    private UserLoginRespDto loginCore(Long memberId, String loginType, String deviceInfor) throws Exception {
        //2.开启事务
        TransactionStatus transactionStatus = manualTransaction.begin();
        try {
            //3.查询当前用户和登录类型是否有登录状态的记录
            UserTokenDo userTokenDo = userTokenMapper.selectByUserIdAndLoginType(memberId, loginType);
            if (userTokenDo != null) {
                // 4.有记录则清除之前的token并修改数据库中的状态
                String token = userTokenDo.getToken();
                // redis 使用事务时暂时不会提交removeToken返回false，需要根据数据库更新是否成功决定是否提交
                tokenUtil.removeToken(token);
                int updateResult = userTokenMapper.updateTokenAvailability(memberId, loginType);
                if (!(updateResult > 0)) {
                    // 数据库没有提交事务前虽然数据还没有被删除，但返回的依然是正确值。若返回不正确，则抛出异常进行回滚。
                    throw new RuntimeException("数据库更新异常");

                }
            } else {
                // 4. 无记录则new一个token记录
                userTokenDo = new UserTokenDo();
                userTokenDo.setUserId(memberId);
                userTokenDo.setLoginType(loginType);
            }
            // 5. 生成新的token
            String token = tokenUtil.createToken(Constants.MEMBER_TOKEN_KEYPREFIX, memberId + "",
                    Constants.MEMBRE_LOGIN_TOKEN_TIME);
            // 6.更新token和设备信息存入在数据库中
            userTokenDo.setToken(token);
            userTokenDo.setDeviceInfor(deviceInfor);
            // redis 使用事务时暂时不会提交新生成的token，但会返回值，也需要根据数据库插入是否成功决定是否提交
            int insertResult = userTokenMapper.insertUserToken(userTokenDo);
            if (!(insertResult >0)) {
                // 数据库没有提交事务前虽然数据还没有被删除，但返回的依然是正确值。若返回不正确，则抛出异常进行回滚。
                throw new RuntimeException("数据库插入异常");
            }
            // 7.返回
            UserLoginRespDto userLoginRespDto = new UserLoginRespDto();
            userLoginRespDto.setUserid(memberId);
            userLoginRespDto.setToken(token);
            // 8.提交事务
            manualTransaction.commit(transactionStatus);
            return userLoginRespDto;
        } catch (Exception e) {
            // 8.回滚事务并抛出异常
            manualTransaction.rollback(transactionStatus);
            throw  e;
        }
    }

}
